package bancosys.tec.utils.date;

import java.math.BigInteger;
import java.util.HashSet;
import java.util.Set;

import jmine.tec.utils.date.WeekDaysEnum;

/**
 * Conversor de dias julianos para data gregoriana, e vice-versa.
 * 
 * @see JulianDay
 * @see Date
 * @author Marco
 */
public class JulianDayConversor {
    private static final int NUM_4715 = 4715;

    private static final double NUM_30_6001 = 30.6001;

    private static final int NUM_365 = 365;

    private static final double NUM_365_25 = 365.25;

    private static final double NUM_122_1 = 122.1;

    private static final int NUM_2439870 = 2439870;

    private static final double NUM_6680_0 = 6680.0;

    private static final int NUM_1524 = 1524;

    private static final int NUM_4 = 4;

    private static final double NUM_36524_25 = 36524.25;

    private static final int N_1867216 = 1867216;

    private static final int NUM_7 = 7;

    private static final double QUARTER = 0.25;

    private static final double NUM_D_0_01 = 0.01;

    private static final double NUM_1720995_0 = 1720995.0;

    private static final double NUM_F_30_6001 = NUM_30_6001;

    private static final double NUM_F_365_25 = NUM_365_25;

    private static final int NUM_13 = 13;

    private static final int NUM_1582 = 1582;

    private static final int NUM_12 = 12;

    private static final int NUM_10 = 10;

    private static final int NUM_31 = 31;

    private static final int NUM_15 = 15;

    private static final BigInteger SEVEN = BigInteger.valueOf(7);

    private static final int JGREG = NUM_15 + NUM_31 * (NUM_10 + NUM_12 * NUM_1582);

    /**
     * Converte uma data para o dia juliano correspondente.
     * 
     * @param date data.
     * @return dia juliano calculado.
     */
    public JulianDay toJulianDay(Date date) {
        int year = date.getYear();
        int month = date.getMonth();
        int day = date.getDay();

        int julianYear = year;
        if (year < 0) {
            julianYear++;
        }
        int julianMonth = month;
        if (month > 2) {
            julianMonth++;
        } else {
            julianYear--;
            julianMonth += NUM_13;
        }

        double julianDay =
                (java.lang.Math.floor(NUM_F_365_25 * julianYear) + java.lang.Math.floor(NUM_F_30_6001 * julianMonth) + day + NUM_1720995_0);
        if (day + NUM_31 * (month + NUM_12 * year) >= JGREG) {
            int ja = (int) (NUM_D_0_01 * julianYear);
            julianDay += 2 - ja + (QUARTER * ja);
        }
        int julianDayInt = (int) java.lang.Math.floor(julianDay);
        WeekDaysEnum weekday = this.calculateWeekday(julianDayInt);
        return new JulianDay(julianDayInt, weekday);
    }

    /**
     * Calcula o dia juliano de um conjunto de datas.
     * 
     * @param dates conjunto de datas.
     * @return conjunto de dias julianos.
     */
    public Set<JulianDay> calculate(Set<Date> dates) {
        Set<JulianDay> julianDays = new HashSet<JulianDay>(dates.size());
        for (Date date : dates) {
            julianDays.add(this.toJulianDay(date));
        }
        return julianDays;
    }

    /**
     * Obtém o dia da semana do dia juliano informado.
     * 
     * @param julian dia juliano.
     * @return dia da semana do dia juliano.
     */
    private WeekDaysEnum calculateWeekday(int julian) {
        BigInteger[] quocientAndRemainder = new BigInteger(String.valueOf(julian)).divideAndRemainder(SEVEN);
        int remainder = quocientAndRemainder[1].intValue();

        int wd = (remainder + 1) % NUM_7;
        return WeekDaysEnum.values()[wd];
        // throw new UtilsComponentRuntimeException("utils.errorFindingDayOfTheWeek", julian);
    }

    /**
     * Converte um dia juliano para uma data correspondente.
     * 
     * @param julianDay dia juliano.
     * @return data calculada.
     */
    public Date toDate(JulianDay julianDay) {
        int ja = julianDay.getDay();
        if (ja >= JGREG) {
            int jalpha = (int) (((ja - N_1867216) - QUARTER) / NUM_36524_25);
            ja = ja + 1 + jalpha - jalpha / NUM_4;
        }

        int jb = ja + NUM_1524;
        int jc = (int) (NUM_6680_0 + ((jb - NUM_2439870) - NUM_122_1) / NUM_365_25);
        int jd = NUM_365 * jc + jc / NUM_4;
        int je = (int) ((jb - jd) / NUM_30_6001);
        int day = jb - jd - (int) (NUM_30_6001 * je);
        int month = je - 1;
        if (month > NUM_12) {
            month = month - NUM_12;
        }
        int year = jc - NUM_4715;
        if (month > 2) {
            year--;
        }
        if (year <= 0) {
            year--;
        }

        return new Date(year, month, day);
    }
}
